package vg.services.data_base_manager.realization;


import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import vg.services.data_base_manager.data.graph.Attribute;
import vg.services.data_base_manager.data.graph.Edge;
import vg.services.data_base_manager.data.graph.Graph;
import vg.services.data_base_manager.data.graph.Vertex;
import vg.services.data_base_manager.data.header.AttributeHeader;
import vg.services.data_base_manager.data.header.EdgeHeader;
import vg.services.data_base_manager.data.header.GraphHeader;
import vg.services.data_base_manager.data.header.GraphModelHeader;
import vg.services.data_base_manager.data.header.VertexHeader;
import vg.services.data_base_manager.data.skeleton.GraphTreeElement;
import vg.services.data_base_manager.interfaces.IModel;

/**
 * This class realizes model, which uses SQL data base.
 * This model may cache graph elements in operating memory.
 */
public class SQLite4JavaCacheModel implements IModel {
	// define max for elements, which place in cache
	private static final int MAX_GRAPH_CACHE = 100;
	private static final int MAX_SUBGRAPH_CACHE = 1000;
	private static final int MAX_VERTEX_CACHE = 1000;
	private static final int MAX_EDGE_CACHE = 1000;
	private static final int MAX_ATTRIBUTE_CACHE = 5000;
	// cache for graph elements
	private Map<Integer, GraphModelHeader> cache_graph_models;
	private Map<Integer, GraphHeader> cache_graphs;
	private Map<Integer, VertexHeader> cache_vertices;
	private Map<Integer, EdgeHeader> cache_edges;
	private Map<Integer, AttributeHeader> cache_attributes;
	// counters
	private int graphModelIdCounter;
	private int graphIdCounter;
	private int vertexIdCounter;
	private int edgeIdCounter;
	private int attributeIdCounter;
	// database data
	private SimpleSQLite4JavaDataBase dataBase;
	// skeleton data
	private Map<Integer, Integer> graph_vertex_link; // <GraphId, VertexId>
	private Map<Integer, String> vertex_name_skeleton; // <VertexId, Name>
	private Map<Integer, String> edge_name_skeleton; // <EdgeId, Name>
	private Map<Integer, Map<Integer, EdgeHeader>> graph_edge_skeleton; // <GraphId, <EdgeId, Edge>>
	private Map<Integer, Map<Integer, VertexHeader>> graph_vertex_skeleton; // <GraphId, <VertexId, Vertex>> 
	private Map<Integer, GraphModelHeader> graph_model_skeleton; // <GraphModelId, GraphModel>
	
	/**
	 * Constructor.
	 */
	public SQLite4JavaCacheModel() {
		create_model();
	}
	
	@Override
	public synchronized int createGraphModelHeader(String name)  {
		GraphModelHeader gh = new GraphModelHeader(graphModelIdCounter++);
		gh.name = name;
		cache_graph_models.put(gh.id, gh);
		update_graph_in_skeleton(gh);
		check_and_commit();
		return gh.id;
	}
	
	@Override
	public synchronized int createGraphModelHeaderWithEmptyRootGraph(String name) {
		GraphModelHeader gh = new GraphModelHeader(graphModelIdCounter++);
		gh.name = name;
		cache_graph_models.put(gh.id, gh);
		gh.root_id = createGraphHeader(gh.id, true);
		update_graph_in_skeleton(gh);		
		check_and_commit();
		return gh.id;
	}
	
	@Override
	public synchronized int createGraphHeader(int graph_id, boolean directed) {
		GraphHeader sgh = new GraphHeader(graphIdCounter++, graph_id);
		sgh.directed = directed;
		cache_graphs.put(sgh.id, sgh);
		update_subgraph_in_skeleton(sgh);
		check_and_commit();
		return sgh.id;
	}
	
	@Override
	public synchronized int createVertexHeader(int subgraph_id, int link2inner_graph) {
		VertexHeader vh = new VertexHeader(vertexIdCounter++, subgraph_id);
		vh.link2inner_graph = link2inner_graph;
		cache_vertices.put(vh.id, vh);
		update_vertex_in_skeleton(vh);
		check_and_commit();
		return vh.id;
	}
	
	@Override
	public synchronized int quickCreateEdgeHeader(int subgraph_id, int source_id, int target_id) {
		EdgeHeader eh = new EdgeHeader(edgeIdCounter++, subgraph_id, source_id, target_id);
		cache_edges.put(eh.id, eh);
		update_edge_in_skeleton(eh);
		check_and_commit();
		return eh.id;
	}
	
	@Override
	public synchronized int createEdgeHeader(int subgraph_id, int source_id, int target_id) {
		// TODO, here need check that vertices are exist
		return quickCreateEdgeHeader(subgraph_id, source_id, target_id);
	}
	
	@Override
	public synchronized int createAttributeHeader4Edge(int edge_id, String name, Object value, Class<?> type) {
		// FIXME
		commit_all();
		
		Edge e = getEdge(edge_id);
		if (e != null && e.getAttribute(name) != null) {
			Attribute attr = e.getAttribute(name);
			AttributeHeader ah = new AttributeHeader(attr.getStorableId(), edge_id, AttributeHeader.DEF_EDGE_OWNER_TYPE);
			ah.name = name;
			ah.value = value;
			ah.type = type;
			modifyAttributeHeader(ah);
			return attr.getStorableId();
		}
		
		AttributeHeader ah = new AttributeHeader(attributeIdCounter++, edge_id, AttributeHeader.DEF_EDGE_OWNER_TYPE);
		ah.name = name;
		ah.value = value;
		ah.type = type;
		cache_attributes.put(ah.id, ah);
		update_attribute_in_skeleton(ah);
		check_and_commit();
		return ah.id;
	}
	
	@Override
	public synchronized int createAttributeHeader4Vertex(int vertex_id, String name, Object value, Class<?> type) {
		// FIXME
		commit_all();
		
		Vertex v = getVertex(vertex_id);
		if (v != null && v.getAttribute(name) != null) {
			Attribute attr = v.getAttribute(name);
			AttributeHeader ah = new AttributeHeader(attr.getStorableId(), vertex_id, AttributeHeader.DEF_VERTEX_OWNER_TYPE);
			ah.name = name;
			ah.value = value;
			ah.type = type;
			modifyAttributeHeader(ah);
			return attr.getStorableId();
		}
		
		AttributeHeader ah = new AttributeHeader(attributeIdCounter++, vertex_id, AttributeHeader.DEF_VERTEX_OWNER_TYPE);
		ah.name = name;
		ah.value = value;
		ah.type = type;
		cache_attributes.put(ah.id, ah);
		update_attribute_in_skeleton(ah);
		check_and_commit();
		return ah.id;
	}
	
	@Override
	public synchronized Graph getSubGraph(List<Integer> vertexIds) {
		commit_all();
		return dataBase.getSubGraph(vertexIds);
	}
	
	@Override
	public synchronized Graph getGraph(int subGraphId) {
		commit_all();
		return dataBase.getGraph(subGraphId);
	}
	
	@Override
	public synchronized Edge getEdge(int edgeId) {
		commit_all();
		return dataBase.getEdge(edgeId);
	}
	
	@Override
	public synchronized Vertex getVertex(int vertexId) {
		commit_all();
		return dataBase.getVertex(vertexId);
	}
	
	@Override
	public synchronized Graph getRootGraphByGraphModelId(int graphModelId) {
		commit_all();
		return dataBase.getRootGraph(graphModelId);
	}
	
	@Override
	public synchronized Attribute getAttribute(int attrId) {
		commit_all();
		return dataBase.getAttribute(attrId);
	}
	
	@Override
	public synchronized VertexHeader getVertexHeader(int vertexId) {
		VertexHeader vh = cache_vertices.get(vertexId);
		if(vh != null) {
			return vh.clone();
		} else {
			return dataBase.getVertexHeader(vertexId);
		}
	}
	
	@Override
	public synchronized AttributeHeader getAttributeHeader(int attributeId) {
		AttributeHeader vh = cache_attributes.get(attributeId);
		if(vh != null) {
			return vh.clone();
		} else {
			return dataBase.getAttributeHeader(attributeId);
		}
	}
	
	@Override
	public synchronized List<Integer> getGraphModelIds() {
		return new ArrayList<Integer>(graph_model_skeleton.keySet());
	}
	
	@Override
	public synchronized GraphModelHeader getGraphModelHeader(int graphModelId) {		
		GraphModelHeader gh = cache_graph_models.get(graphModelId);
		if(gh != null) {
			return gh.clone();
		} else {
			return dataBase.getGraphModelHeader(graphModelId);
		}		
	}
	
	@Override
	public synchronized GraphHeader getGraphHeader(int graphId) {
		GraphHeader gh = cache_graphs.get(graphId);
		if(gh != null) {
			return gh.clone();
		} else {
			return dataBase.getGraphHeader(graphId);
		}		
	}
	
	@Override
	public synchronized GraphHeader getGraphHeader4Vertex(int vertexId) {
		VertexHeader vh = dataBase.getVertexHeader(vertexId);
		if (vh == null) 
			return null;
		return dataBase.getGraphHeader(vh.subgraph_id);
	}
	
	@Override
	public synchronized List<List<Object>> executeSQLRequest(String request) {
		check_and_commit();
		return dataBase.executeSQLRequest(request);
	}
	
	@Override
	public synchronized GraphTreeElement getGraphModelSkeleton(int graphModelId) {		
		return create_skeleton(graphModelId);
	}
	
	@Override
	public synchronized void quit() {
		dataBase.close();
	}
	
	@Override
	public synchronized void resetModel() {
		quit();
		create_model();
	}
	
	@Override
	public void closeGraph(int grapgId) {
		
	}
	
	@Override
	public synchronized Set<Integer> getAllVertexIds4SubGraph(Graph subGraph) {
		Set<Integer> result_ids = new HashSet<Integer>();
		Queue<Integer> current_ids = new ArrayDeque<Integer>();
		
		Collection<Vertex> tmp_vert_list = subGraph.getVertices();
		for(Vertex buf_v : tmp_vert_list) {
			current_ids.add(buf_v.getStorableId());
		}
		
		while(!current_ids.isEmpty()) {
			int e = current_ids.poll();
			result_ids.add(e);
			// FIXME It is very slow part of code. Need fix it.
			VertexHeader vh = getVertexHeader(e);
			if(vh.link2inner_graph >= 0) {
				 Map<Integer, VertexHeader> map_sg_vert = graph_vertex_skeleton.get(vh.link2inner_graph);
				 current_ids.addAll(map_sg_vert.keySet());
			}
		}
		
		return result_ids;
	}
	
	@Override
	public synchronized Set<Integer> getAllVertexIds4Graph(int graphId) {
		Set<Integer> result_ids = new HashSet<Integer>();
		Queue<Integer> current_ids = new ArrayDeque<Integer>();
		
		GraphModelHeader gh = graph_model_skeleton.get(graphId);
		Map<Integer, VertexHeader> tmp_vert_map = graph_vertex_skeleton.get(gh.root_id);
		
		if(tmp_vert_map != null)
			current_ids.addAll(tmp_vert_map.keySet());
		
		while(!current_ids.isEmpty()) {
			int e = current_ids.poll();
			result_ids.add(e);
			// FIXME It is very slow part of code. Need fix it.
			VertexHeader vh = getVertexHeader(e);
			if(vh.link2inner_graph >= 0) {
				 Map<Integer, VertexHeader> map_sg_vert = graph_vertex_skeleton.get(vh.link2inner_graph);
				 current_ids.addAll(map_sg_vert.keySet());
			}
		}
		
		return result_ids;		
	}
	
//=============================================================================
//--------------Modifying methods----------------------------------------------
	@Override
	public synchronized boolean modifyGraphModelHeader(GraphModelHeader newGraphModelHeader) {
		GraphModelHeader old_graph_header = cache_graph_models.get(newGraphModelHeader.id);
		update_graph_in_skeleton(newGraphModelHeader);
		if(old_graph_header != null) {
			cache_graph_models.put(newGraphModelHeader.id, newGraphModelHeader);
			return true;
		} else {
			return dataBase.modifyGraphHeader(newGraphModelHeader);
		}
	};
	
	@Override
	public synchronized boolean modifyVertexHeader(VertexHeader newVertexHeader) {
		VertexHeader old_vertex_header = cache_vertices.get(newVertexHeader.id);
		update_vertex_in_skeleton(newVertexHeader);
		if(old_vertex_header != null) {
			cache_vertices.put(newVertexHeader.id, newVertexHeader);
			return true;
		} else {
			return dataBase.modifyVertexHeader(newVertexHeader);
		}
	};
	
	@Override
	public synchronized boolean modifyAttributeHeader(AttributeHeader newAttributeHeader) {
		AttributeHeader old_attr_header = cache_attributes.get(newAttributeHeader.id);
		update_attribute_in_skeleton(newAttributeHeader);
		if(old_attr_header != null) {
			cache_attributes.put(newAttributeHeader.id, newAttributeHeader);
			return true;
		} else {
			return dataBase.modifyAttributeHeader(newAttributeHeader);
		}
	};
	
//=============================================================================
//--------------OTHER METHODS--------------------------------------------------
	@Override
	public synchronized String generateTitle4SubGraph(Graph subGraph) {
		// Note: doesn't work if subgraph will empty and 
		// subgraph id will equals -1, that it returns null
		if(subGraph == null) return null;
		
		int sg_id = subGraph.getStorableId();
		
		if(sg_id < 0) {
			Collection<Vertex> v_list = subGraph.getVertices();
			if(v_list != null && !v_list.isEmpty()) {
				GraphHeader sg_header = getGraphHeader4Vertex(v_list.iterator().next().getStorableId());
				if(sg_header != null)
					sg_id = sg_header.id;
			}
		}
		
		// optimization 
		if(sg_id >= 0) {
			Integer v_id = graph_vertex_link.get(sg_id);
			if(v_id == null) {
				for(GraphModelHeader buf : graph_model_skeleton.values()) {
					if(buf.root_id == sg_id) {
						v_id = buf.root_id;
						return buf.name; // return graph name
					}
				}
			} else {
				return vertex_name_skeleton.get(v_id); // return vertex name
			}
		}
		return null;
	}

	@Override
	public synchronized int generateGraphId4SubGraph(Graph subGraph) {
		if(subGraph == null) return -1;
		
		int sg_id = subGraph.getStorableId();
		
		if(sg_id < 0) {
			Collection<Vertex> v_list = subGraph.getVertices();
			if(v_list != null && !v_list.isEmpty()) {
				GraphHeader sg_header = getGraphHeader4Vertex(v_list.iterator().next().getStorableId());
				if(sg_header != null)
					return sg_header.graph_model_id;
				return -1;
			}
		}

		if(sg_id < 0) {
			GraphHeader sg_header = dataBase.getGraphHeader(sg_id);
			if(sg_header != null)
				return sg_header.graph_model_id;
			return -1;
		}
		return -1;
	}
	
	@Override
	public synchronized int generateSubGraphId4SubGraph(Graph subGraph) {
		if(subGraph == null) return -1;
		
		int sg_id = subGraph.getStorableId();
		
		if(sg_id < 0) {
			Collection<Vertex> v_list = subGraph.getVertices();
			if(v_list != null && !v_list.isEmpty()) {
				GraphHeader sg_header = getGraphHeader4Vertex(v_list.iterator().next().getStorableId());
				if(sg_header != null)
					return sg_header.graph_model_id;
				return -1;
			}
		} else {
			return sg_id;
		}
		return -1;
	}
	
//=============================================================================
//--------------Methods for working with attributes----------------------------
	@Override
	public synchronized Map<String, Class<?>> getVertexAttributes4GraphModel(int graphModelId) {
		check_and_commit();
		return dataBase.getVertexAttributes4GraphModel(graphModelId);
	}
	
	@Override
	public synchronized Map<String, Class<?>> getEdgeAttributes4GraphModel(int graphModelId) {
		check_and_commit();
		return dataBase.getEdgeAttributes4GraphModel(graphModelId);
	}
	
//=============================================================================
//--------------PRIVATE METHODS------------------------------------------------
	private void check_and_commit() {
		if(cache_graph_models.size() > MAX_GRAPH_CACHE) {
			dataBase.addGraphModelHeaders(cache_graph_models.values());
			cache_graph_models.clear();
		}
		
		if(cache_graphs.size() > MAX_SUBGRAPH_CACHE) {
			dataBase.addGraphHeaders(cache_graphs.values());
			cache_graphs.clear();
		}
		
		if(cache_vertices.size() > MAX_VERTEX_CACHE) {
			dataBase.addVertexHeaders(cache_vertices.values());
			cache_vertices.clear();
		}
		
		if(cache_edges.size() > MAX_EDGE_CACHE) {
			dataBase.addEdgeHeaders(cache_edges.values());
			cache_edges.clear();
		}
		
		if(cache_attributes.size() > MAX_ATTRIBUTE_CACHE) {
			dataBase.addAttributeHeaders(cache_attributes.values());
			cache_attributes.clear();
		}
	}
	
	private void commit_all() {
		dataBase.addGraphModelHeaders(cache_graph_models.values());
		cache_graph_models.clear();
		
		dataBase.addGraphHeaders(cache_graphs.values());
		cache_graphs.clear();
		
		dataBase.addVertexHeaders(cache_vertices.values());
		cache_vertices.clear();

		dataBase.addEdgeHeaders(cache_edges.values());
		cache_edges.clear();
		
		dataBase.addAttributeHeaders(cache_attributes.values());
		cache_attributes.clear();
	}
	
	private void create_model() {
		try {
			dataBase = new SimpleSQLite4JavaDataBase();
			// cache
			cache_graph_models = new HashMap<Integer, GraphModelHeader>();
			cache_graphs = new HashMap<Integer, GraphHeader>();
			cache_vertices = new HashMap<Integer, VertexHeader>();
			cache_edges = new HashMap<Integer, EdgeHeader>();
			cache_attributes = new HashMap<Integer, AttributeHeader>();
			// counters
			graphModelIdCounter = 0;
			graphIdCounter = 0;
			vertexIdCounter = 0;
			edgeIdCounter = 0;
			attributeIdCounter = 0;
			// skeleton
			graph_vertex_link = new HashMap<Integer, Integer>();
			vertex_name_skeleton = new HashMap<Integer, String>();
			edge_name_skeleton = new HashMap<Integer, String>();
			graph_vertex_skeleton = new HashMap<Integer, Map<Integer,VertexHeader>>();
			graph_edge_skeleton = new HashMap<Integer, Map<Integer,EdgeHeader>>();
			graph_model_skeleton = new HashMap<Integer, GraphModelHeader>();
		} catch (Throwable ex) {		
	    	//throw(new CoreException("[" + this.getClass().getName() + ".createModel] [FAIL] Exception = " + ex.getMessage(),EnumCriticalityException.FAILED));
		}
	}
	
	private void update_subgraph_in_skeleton(GraphHeader header) {
		if(!graph_vertex_skeleton.containsKey(header.id)) { 
			// it's may be slowly than HashMap, but we have got order map
			graph_vertex_skeleton.put(header.id, new LinkedHashMap<Integer, VertexHeader>());
			graph_edge_skeleton.put(header.id, new LinkedHashMap<Integer, EdgeHeader>());
		}
	}

	private void update_vertex_in_skeleton(VertexHeader header) {
		Map<Integer, VertexHeader> curr_vertex_map = graph_vertex_skeleton.get(header.subgraph_id);
		if(curr_vertex_map != null) {
			curr_vertex_map.put(header.id, header);
			if(header.link2inner_graph != VertexHeader.DEF_NO_LINK2INNER_GRAPH) {
				graph_vertex_link.put(header.link2inner_graph, header.id);
			}
		}
	}
	
	private void update_edge_in_skeleton(EdgeHeader header) {
		Map<Integer, EdgeHeader> curr_edge_map = graph_edge_skeleton.get(header.subgraph_id);
		if(curr_edge_map != null) {
			curr_edge_map.put(header.id, header);
		}
	}
	
	private void update_graph_in_skeleton(GraphModelHeader header) {
		graph_model_skeleton.put(header.id, header);
	}
	
	private void update_attribute_in_skeleton(AttributeHeader header) {
		if(header.name != null && header.name.equals("name") && header.value != null) {
			switch(header.owner_type) {
				case AttributeHeader.DEF_VERTEX_OWNER_TYPE: {
					vertex_name_skeleton.put(header.owner_id, header.value.toString());
					break;
				}
				case AttributeHeader.DEF_EDGE_OWNER_TYPE: {
					edge_name_skeleton.put(header.owner_id, header.value.toString());
					break;
				}
			}
		}
	}
	
	private GraphTreeElement create_skeleton(int graph_id) {
		GraphModelHeader graph = graph_model_skeleton.get(graph_id);
		// FIX ME. Need to devise and implement mechanism, which doesn't require root_id 
		if(graph != null && graph.root_id >= 0) {
			GraphTreeElement r = new GraphTreeElement();
			r.vg_setGraph_id_tmp(graph.root_id);
			r.vg_setGraphModel_id_tmp(graph.id);
			r.vg_setName(graph.name);
			create_skeleton_part(graph.root_id, r);
			return r;
		}
		return null;
	}
	
	// FIX ME. Need mechanism without recursive function 
	private void create_skeleton_part(int subgraph_id, GraphTreeElement curr) {
		Map<Integer, VertexHeader> curr_vertex_map = graph_vertex_skeleton.get(subgraph_id);
		if(curr_vertex_map != null) {
			for(VertexHeader buf : curr_vertex_map.values()) {
				GraphTreeElement e = new GraphTreeElement();
				e.vg_setVertex_id(buf.id);
				e.vg_setName(vertex_name_skeleton.get(buf.id));
				if(buf.link2inner_graph >= 0) {
					e.vg_setGraph_id_tmp(buf.link2inner_graph);
					create_skeleton_part(buf.link2inner_graph, e);
				}
				curr.vg_add(e);
			}
		}
	}
}
